﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml;

namespace IHISCETeim.Library
{
    /// <summary>
    /// 使用文件系统的缓存
    /// 使用此缓存将在系统所在的文件夹的根目录下创建webdatacache文件夹用于存储缓存文件
    /// 所以需要写入权限
    /// </summary>
    public class FileCacheStrategy : ICacheStrategy
    {
        private string WebCacheFilePath = "~/webdatacache/";
        private string WebCacheFileConfig = "~/webdatacache/WebCacheFileConfie.config";
        private static XmlDocument rootXml = new XmlDocument();
        private static XmlElement objectXmlMap;
        /// <summary>
        /// 构造
        /// </summary>
        public FileCacheStrategy()
        {
            try
            {
                //创建文件夹
                string webcachefile = request.MapPath(WebCacheFilePath);
                if (!System.IO.Directory.Exists(webcachefile))
                {
                    System.IO.Directory.CreateDirectory(webcachefile);
                }
                //处理缓存依赖及过期存储文件
                string webcacheFileConfig = request.MapPath(WebCacheFileConfig);
                if (!System.IO.File.Exists(webcacheFileConfig))
                {
                    FileStream fileStream = System.IO.File.Create(webcacheFileConfig);
                    fileStream.Close();
                    objectXmlMap = rootXml.CreateElement("Cache");
                    //建立内部XML文档.
                    rootXml.AppendChild(objectXmlMap);
                }
                else
                {
                    //加载已存在配置文件
                    rootXml.Load(webcacheFileConfig);
                    objectXmlMap = (XmlElement)rootXml.SelectSingleNode("/Cache");

                }
            }
            catch
            { 
            }
        }


        #region 私有属性/方法文件处理
        private void SavaXml() {
            string webcacheFileConfig = request.MapPath(WebCacheFileConfig);
            rootXml.Save(webcacheFileConfig);
        }

        private HttpRequest request {
            get {
                return System.Web.HttpContext.Current.Request;
            }
        }

        /// <summary>
        /// 整理 xpath 确保 '/'被删除 is removed
        /// </summary>
        /// <param name="xpath">分级地址</param>
        /// <returns></returns>
        private string PrepareXpath(string xpath)
        {
            
                string[] xpathArray = xpath.Split('/');
                xpath = "/Cache";
                foreach (string s in xpathArray)
                {
                    if (s != "")
                    {
                        xpath = xpath + "/" + s;
                    }
                }
                return xpath;
            
        }

        /// <summary>
        /// 对象树形分级对象节点
        /// </summary>
        /// <param name="xpath">分级路径 location</param>
        /// <returns></returns>
        private XmlNode CreateNode(string xpath)
        {
            
                string[] xpathArray = xpath.Split('/');
                string root = "";
                XmlNode parentNode = objectXmlMap;
                //建立相关节点
                for (int i = 1; i < xpathArray.Length; i++)
                {
                    XmlNode node = objectXmlMap.SelectSingleNode(root + "/" + xpathArray[i]);
                    // 如果当前路径不存在则建立,否则设置当前路径到它的子路径上
                    if (node == null)
                    {
                        XmlElement newElement = objectXmlMap.OwnerDocument.CreateElement(xpathArray[i]);
                        parentNode.AppendChild(newElement);
                    }
                    //设置低一级的路径
                    root = root + "/" + xpathArray[i];
                    parentNode = objectXmlMap.SelectSingleNode(root);
                }
                return parentNode;
            
        }

        private object GetFile(string filename) {
            FileStream fileStream = null;
            try
            {
                
                if (string.IsNullOrEmpty(filename))
                    return null;
                string filepath = request.MapPath(WebCacheFilePath + filename + ".cache");

                string _timeout = "";
                XmlNode node = rootXml.SelectSingleNode(PrepareXpath(filename));
                if (node != null)
                {
                    _timeout = node.Attributes["timeout"].Value;
                    DateTime lastmodifydt = File.GetLastWriteTime(filepath);
                    DateTime timeoutdt = lastmodifydt.AddMinutes(Convert.ToDouble(_timeout));
                    if (timeoutdt < DateTime.Now)
                    {
                        DelteFile(filename);
                        return null;
                    }
                }              

                fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read); 
                BinaryFormatter b = new BinaryFormatter();//SoapFormatter
                object c = b.Deserialize(fileStream);
                fileStream.Close();
                return c;
            }
            catch
            {
                return null;
            }
            finally
            {
                if(fileStream!=null)
                    fileStream.Close();
            }
        }

        private void WriteFile(string filename, object obj, int timeoutminute)
        {
            FileStream fileStream = null;
            try
            {

                #region
                //当缓存到期时间为0或负值,则不再放入缓存
                if (timeoutminute <= 0) return;

                //整理XPATH表达式信息
                string newXpath = PrepareXpath(filename);
                int separator = newXpath.LastIndexOf("/");
                //找到相关的组名
                string group = newXpath.Substring(0, separator);
                //找到相关的对象
                string element = newXpath.Substring(separator + 1);

                XmlNode groupNode = objectXmlMap.SelectSingleNode(group);

                //建立对象的唯一键值, 用以映射XML和缓存对象的键
                string _timeout = "";

                XmlNode node = objectXmlMap.SelectSingleNode(PrepareXpath(filename));
                if (node != null)
                {
                    _timeout = node.Attributes["timeout"].Value;
                }

                if (_timeout == "")
                {
                    groupNode = CreateNode(group);
                    _timeout = timeoutminute.ToString();
                    //建立新元素和一个属性 for this perticular object
                    XmlElement objectElement = objectXmlMap.OwnerDocument.CreateElement(element);
                    XmlAttribute objectAttribute = objectXmlMap.OwnerDocument.CreateAttribute("timeout");
                    objectAttribute.Value = _timeout;
                    objectElement.Attributes.Append(objectAttribute);

                    objectAttribute = objectXmlMap.OwnerDocument.CreateAttribute("cachekey");
                    objectAttribute.Value = filename;
                    objectElement.Attributes.Append(objectAttribute);

                    //为XML文档建立新元素
                    groupNode.AppendChild(objectElement);
                }              
                SavaXml();
                #endregion

                string filepath = request.MapPath(WebCacheFilePath + filename+".cache");
                if (!File.Exists(filepath))
                {
                    int lastpos = filepath.LastIndexOf("\\");
                    string _fp = filepath.Substring(0, lastpos);
                    Directory.CreateDirectory(_fp);
                    //File.Create(filepath);
                }

                fileStream = new FileStream(filepath, FileMode.Create);
                BinaryFormatter b = new BinaryFormatter();
                b.Serialize(fileStream, obj);
                
            }
            catch
            {
                
            }
            finally
            {
                if (fileStream != null)
                    fileStream.Close();
            }
        }

        private void DelteFile(string filename) {
            try
            {
                string filepath = request.MapPath(WebCacheFilePath + filename + ".cache");

                XmlNode result = rootXml.SelectSingleNode(PrepareXpath(filename));

                //检查路径是否指向一个组或一个被缓存的实例元素
                if (result.HasChildNodes)
                {
                    //删除所有对象和子结点的信息
                    XmlNodeList objects = result.SelectNodes("*[@cachekey]");
                    string objectId = "";
                    foreach (XmlNode node in objects)
                    {
                        objectId = node.Attributes["cachekey"].Value;
                        //删除对象
                        DelteFile(objectId);
                        node.ParentNode.RemoveChild(node);
                        SavaXml();
                    }
                    DelteFile(filename);
                    result.ParentNode.RemoveChild(result);
                    SavaXml();
                }
                else
                {
                    File.Delete(filepath);
                    
                }
            }
            catch
            {

            }
            finally
            {
                
            }
        }
        #endregion

        // 默认缓存存活期为1440分钟(24小时)
        protected int _timeOut = -1;
        /// <summary>
        /// 设置到期相对时间[单位:分钟] 
        /// </summary>
        public virtual int TimeOut
        {
            set { _timeOut = value > 0 ? value : 6000; }
            get { return _timeOut > 0 ? _timeOut : 6000; }
        }

        public void Add(string CacheKey, object o)
        {
            WriteFile(CacheKey, o,TimeOut);
        }

        public void Add(string CacheKey, object o, int timeoutminute)
        {
            WriteFile(CacheKey, o, timeoutminute);
        }
        /// <summary>
        /// 此方法和Add(string CacheKey, object o)效果一样
        /// </summary>
        /// <param name="CacheKey"></param>
        /// <param name="o"></param>
        /// <param name="dependKey"></param>
        public void Add(string CacheKey, object o, string[] dependKey)
        {
            WriteFile(CacheKey, o,TimeOut);
        }

        public void Remove(string CacheKey)
        {
            DelteFile(CacheKey);
        }

        public object Get(string CacheKey)
        {
            return GetFile(CacheKey);
        }

        public bool Clear()
        {
            //Remove("Cache");
            string webcachefile = request.MapPath(WebCacheFilePath);
            if (!System.IO.Directory.Exists(webcachefile))
            {
                return false;
            }
            try
            {
                if (Directory.Exists(webcachefile))
                {
                    Directory.Delete(webcachefile, true);
                    Directory.CreateDirectory(webcachefile);                   
                }
                return true;
            }
            catch
            {
                return false;
            }

        }

    }
}
